/** @file
  Platform Emmc Dxe driver to configure Emmc Hs400 Tuning
  and set variable for Emmc Hs400 Tuning Data.

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2014 - 2016 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/
#include "PlatformEmmcDxe.h"

/**
  After EFI_EMMC_CARD_INFO_PROTOCOL and PCH_EMMC_TUNING_PROTOCOL installed,
  configure platform Emmc for Hs400 Tuning and set variable for HS400 Tuning Data.

  @param[in] Event                A pointer to the Event that triggered the callback.
  @param[in] Context              A pointer to private data registered with the callback function.

**/
VOID
EFIAPI
ConfigurePlatformEmmc (
  IN EFI_EVENT                  Event,
  IN VOID                       *Context
  )
{
  EFI_EMMC_CARD_INFO_PROTOCOL       *EfiEmmcApplicationRegister;
  PCH_EMMC_TUNING_PROTOCOL          *PchEmmcTuningProtocol;
  EMMC_INFO                         EmmcInfo;
  EMMC_TUNING_DATA                  EmmcTuningData;
  PLATFORM_EMMC_TUNING_DATA         PlatformEmmcTuningData;
  UINT8                             PchEmmcTuningProtocolRevision;
  EFI_STATUS                        Status;

  PchEmmcTuningProtocolRevision = PCH_EMMC_TUNING_PROTOCOL_REVISION;

  DEBUG ((DEBUG_INFO, "ConfigurePlatformEmmc Start()\n"));

  Status = gBS->LocateProtocol (
              &gEfiEmmcCardInfoProtocolGuid,
              NULL,
              (VOID **) &EfiEmmcApplicationRegister
              );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (
                  &gPchEmmcTuningProtocolGuid,
                  NULL,
                  (VOID **) &PchEmmcTuningProtocol
                  );
  ASSERT_EFI_ERROR (Status);
  //
  // Return if eMMC device does not support HS400
  //
  if (((EfiEmmcApplicationRegister->CardData->ExtCSDRegister.CARD_TYPE & B_EMMC_HS400_12V) == FALSE) &&
     ((EfiEmmcApplicationRegister->CardData->ExtCSDRegister.CARD_TYPE & B_EMMC_HS400_18V) == FALSE)) {
    return;
  }

  //
  // Get EmmcInfo from EmmcCardInfoProtocol
  // EmmcCardInfoProtocol is produced in the driver
  //
  EmmcInfo.PartitionHandle = EfiEmmcApplicationRegister->CardData->Partitions->Handle;
  EmmcInfo.Lba = 0x2000000;
  EmmcInfo.RelativeDevAddress = (EfiEmmcApplicationRegister->CardData->Address << 16);

  if (EfiEmmcApplicationRegister->CardData->CurrentBusWidth == 4) {
    EmmcInfo.HS200BusWidth = V_EMMC_HS200_BUS_WIDTH_4;
  } else{
    EmmcInfo.HS200BusWidth = V_EMMC_HS200_BUS_WIDTH_8;
  }

  //
  // For normal boot flow
  // 1. If ScsEmmcEnabled and ScsEmmcHs400Enabled policy set,
  //    a) Set ScsEmmcHs400TuningRequired policy to state tuning required in PEI,
  //       - if RTC_PWR_STS bit is set which indicates a new coin-cell battery insertion, a battery failure or CMOS clear.(Boot with default settings)
  //       - if non-volatile variable 'Hs400TuningData' does not exist
  //    b) RC installed Pch Emmc Tuning Protocol regardless of ScsEmmcHs400TuningRequired policy setting.in DXE
  //    c) If boot with default settings after CMOS cleared, platform delete variable 'Hs400TuningData' in DXE
  // 2. Once RC successfully installed Pch Emmc Tuning Protocol, it will be used to perform EmmcTune for Hs400.
  // 3. Then, platform must set the variable with returned EmmcTuningData no matter tuning pass of fail
  // 4. Platform shall set variable 'Hs400TuningData' for one time only or after CMOS clear
  //
  // For fast boot flow
  // 1. If ScsEmmcEnabled and ScsEmmcHs400Enabled policy set,
  //    a) Set ScsEmmcHs400TuningRequired policy to state tuning not required, if non-volatile variable 'Hs400TuningData' exist
  //    b) RC installed Pch Emmc Tuning Protocol regardless of ScsEmmcHs400TuningRequired policy setting in DXE
  // 2. Once RC successfully installed Pch Emmc Tuning Protocol, it will be used to perform EmmcTune
  // 3. Since ScsEmmcHs400TuningRequired state tuning not required, RC will not perform Emmc Hs400 Tuning but just set the device to operate in HS400 mode if data is valid
  // 4. Platform shall not set variable 'Hs400TuningData'
  //
  // For both normal boot and fast boot flow
  // If platform found no Pch Emmc Tuning protocol installed, this DXE module will not dispatch
  //

  Status = PchEmmcTuningProtocol->EmmcTune (PchEmmcTuningProtocol, PchEmmcTuningProtocolRevision, &EmmcInfo, &EmmcTuningData);

  if (EmmcTuningData.Hs400DataValid == TRUE) {
    //
    // Save variable with HS400 tuned values
    //
    PlatformEmmcTuningData.Hs400DataValid      = EmmcTuningData.Hs400DataValid;
    PlatformEmmcTuningData.Hs400RxStrobe1Dll   = EmmcTuningData.Hs400RxStrobe1Dll;
    PlatformEmmcTuningData.Hs400TxDataDll      = EmmcTuningData.Hs400TxDataDll;
    PlatformEmmcTuningData.Hs400DriverStrength = EmmcTuningData.Hs400DriverStrength;

    DEBUG ((DEBUG_INFO, "ConfigurePlatformEmmc::SetVariable(gPlatformEmmcHs400TuningInfoGuid):\n"));
    DEBUG ((DEBUG_INFO, "Hs400DataValid = 0x%x\n", PlatformEmmcTuningData.Hs400DataValid));
    DEBUG ((DEBUG_INFO, "Hs400RxStrobe1Dll = 0x%x\n", PlatformEmmcTuningData.Hs400RxStrobe1Dll));
    DEBUG ((DEBUG_INFO, "Hs400TxDataDll = 0x%x\n", PlatformEmmcTuningData.Hs400TxDataDll));
    DEBUG ((DEBUG_INFO, "Hs400DriverStrength = 0x%x\n", PlatformEmmcTuningData.Hs400DriverStrength));

    Status = gRT->SetVariable (
                    HS400_TUNING_DATA_VAR,
                    &gPlatformEmmcHs400TuningInfoGuid,
                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                    sizeof (PLATFORM_EMMC_TUNING_DATA),
                    &PlatformEmmcTuningData
                    );
    ASSERT_EFI_ERROR (Status);
  }

  DEBUG ((DEBUG_INFO, "ConfigurePlatformEmmc End()\n"));

}

/**
  Entry point for the driver.

  This routine gets info from the EFI_EMMC_CARD_INFO_PROTOCOL and perform
  Platform Emmc Hs400 mode tuning.

  @param[in] ImageHandle              Image Handle.
  @param[in] SystemTable              EFI System Table.

**/
EFI_STATUS
EFIAPI
PlatformEmmcInit (
  IN EFI_HANDLE                         ImageHandle,
  IN EFI_SYSTEM_TABLE                   *SystemTable
  )
{
  EFI_STATUS                        Status;
  PCH_SETUP                         PchSetup;
  UINTN                             VariableSize;
  PLATFORM_EMMC_TUNING_DATA         PlatformEmmcTuningData;
  EFI_EVENT                         EmmcApplicationRegisterEvent;
  VOID                              *EmmcApplicationRegisterEventRegistration;

  //
  // Delete Variable 'Hs400TuningData' if boot with default settings after CMOS clear
  //
  if (GetBootModeHob() == BOOT_WITH_DEFAULT_SETTINGS) {
    VariableSize = sizeof (PLATFORM_EMMC_TUNING_DATA);
    Status = gRT->GetVariable (
                    HS400_TUNING_DATA_VAR,
                    &gPlatformEmmcHs400TuningInfoGuid,
                    NULL,
                    &VariableSize,
                    &PlatformEmmcTuningData
                    );

    if (Status == EFI_SUCCESS) {
      Status = gRT->SetVariable (
                      HS400_TUNING_DATA_VAR,
                      &gPlatformEmmcHs400TuningInfoGuid,
                      EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                      0,
                      NULL
                      );
      ASSERT_EFI_ERROR (Status);
    }
  }

  VariableSize = sizeof (PCH_SETUP);
  Status = gRT->GetVariable (
                  L"PchSetup",
                  &gPchSetupVariableGuid,
                  NULL,
                  &VariableSize,
                  &PchSetup
                  );
  ASSERT_EFI_ERROR (Status);

  if ((PchSetup.PchScsEmmcEnabled == FALSE) || (PchSetup.PchScsEmmcHs400Enabled == FALSE)) {
    return EFI_ABORTED;
  }
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  ConfigurePlatformEmmc,
                  NULL,
                  &EmmcApplicationRegisterEvent
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->RegisterProtocolNotify (
                  &gEfiEmmcCardInfoProtocolGuid,
                  EmmcApplicationRegisterEvent,
                  &EmmcApplicationRegisterEventRegistration
                  );
  ASSERT_EFI_ERROR (Status);

  return EFI_SUCCESS;
}

